package models

import (
	"bigdevops/src/config"
	"fmt"
	"github.com/gammazero/workerpool"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

/*
用户相关的数据库字段
基于前端依赖的 user 字段
  {
   id: 1,
   orderNo: 1,
   roleName: '超级管理员',
   roleValue: RoleEnum.SUPER,
   createTime: '@datetime',
   remark: '@cword(10,20)',
   menu: ['1', '1-2', '1-3', '1-4', '1-7'],
   status: '1',
 },
*/

// 角色结构体，包含角色的相关信息
type Role struct {
	Model             // 嵌入 Model 结构体，自动包含 ID 和 CreatedAt 字段
	OrderNo   int     `json:"orderNo" gorm:"comment:排序"`                                    // 排序号
	RoleName  string  `json:"roleName" gorm:"type:varchar(100);uniqueIndex;comment:角色中文名称"` // 角色中文名称，唯一索引
	RoleValue string  `json:"roleValue"  gorm:"type:varchar(100);uniqueIndex;comment:角色值"`  // 角色值，唯一索引
	Remark    string  `json:"remark" gorm:"comment:用户描述"`                                   // 用户描述
	HomePath  string  `json:"homePath" gorm:"comment:登陆后的默认首页"`                             // 登录后的默认首页路径
	Status    string  `json:"status" gorm:"default:1;comment:角色是否被冻结 1正常 2冻结"`              // 角色状态，1正常，2冻结
	Users     []*User `json:"users" gorm:"many2many:user_roles;"`                           // 多对多关系，角色对应的用户
	Menus     []*Menu `json:"menus" gorm:"many2many:role_menus;"`                           // 多对多关系，角色对应的菜单
	Apis      []*Api  `json:"apis" gorm:"many2many:role_apis;"`                             // 多对多关系，角色对应的API
	MenuIds   []int   `json:"menuIds" gorm:"-"`                                             // 给前端用的菜单ID列表，不保存到数据库
	ApiIds    []int   `json:"apiIds" gorm:"-"`                                              // 给前端用的API ID列表，不保存到数据库
}

// 获取所有角色信息，包括关联的菜单、用户和API
func GetRollAll() (roles []*Role, err error) {
	err = Db.Preload("Menus").Preload("Users").Preload("Apis").Find(&roles).Error
	return
}

// 根据角色值获取角色信息，包括关联的菜单
func GetRoleByRoleValue(roleValue string) (*Role, error) {
	var dbRole Role
	err := Db.Where("role_value = ?", roleValue).Preload("Menus").First(&dbRole).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("角色不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbRole, nil
}

// 根据角色ID获取角色信息，包括关联的菜单
func GetRoleById(id int) (*Role, error) {
	var dbRole Role
	err := Db.Where("id = ?", id).Preload("Menus").First(&dbRole).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("角色不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbRole, nil
}

// 创建一个新角色
func (obj *Role) CreateOne() error {
	return Db.Create(obj).Error
}

// 更新角色的菜单关联信息
func (obj *Role) UpdateMenus(menus []*Menu) error {
	// 更新角色信息
	err1 := Db.Where("id = ?", obj.ID).Updates(obj).Error
	// 更新角色和菜单的关联信息
	err2 := Db.Model(obj).Association("Menus").Replace(menus)
	if err1 == nil && err2 == nil {
		return nil
	} else {
		return fmt.Errorf("更新本体:%w 更新关联:%w", err1, err2)
	}
}

// 更新角色的API关联信息
func (obj *Role) UpdateApis(apis []*Api, sc *config.ServerConfig) error {
	// 更新角色信息
	err1 := Db.Where("id = ?", obj.ID).Updates(obj).Error
	// 更新角色和API的关联信息
	err2 := Db.Model(obj).Association("Apis").Replace(apis)

	// 处理所有的 policy
	rules := [][]string{}
	wp := workerpool.New(100)
	for _, api := range apis {
		api := api
		wp.Submit(func() {
			oneRule := []string{
				obj.RoleValue,
				api.Path,
				api.Method,
			}
			// 处理ALL的情况
			if api.Method == "ALL" {
				methods := []string{
					"GET",
					"POST",
					"DELETE",
				}
				for _, m := range methods {
					pass, err := CasbinCheckPermission(obj.RoleValue, api.Path, m)
					if pass {
						continue
					}
					_, err = CasbinAddOnePolicy(obj.RoleValue, api.Path, m)
					if err != nil {
						sc.Logger.Error("CasbinAddOnePolicy错误",
							zap.Error(err),
							zap.String("角色", obj.RoleValue),
							zap.String("api.Path", api.Path),
							zap.String("api.Method", api.Method),
						)
					}
				}
			} else {
				pass, err := CasbinCheckPermission(obj.RoleValue, api.Path, api.Method)
				if pass {
					return
				}
				_, err = CasbinAddOnePolicy(obj.RoleValue, api.Path, api.Method)
				if err != nil {
					sc.Logger.Error("CasbinAddOnePolicy错误",
						zap.Error(err),
						zap.String("角色", obj.RoleValue),
						zap.String("api.Path", api.Path),
						zap.String("api.Method", api.Method),
					)
				}
			}
			rules = append(rules, oneRule)
		})
	}
	wp.StopWait()
	// TODO? 这里的CasbinAddPolicies的意义在哪里？
	//_, err3 := CasbinAddPolicies(rules)

	if err1 == nil && err2 == nil {
		return nil
	} else {
		return fmt.Errorf("更新本体:%w 更新关联:%w", err1, err2)
	}
}

// 更新角色的API关联信息，不更新角色本身信息
func (obj *Role) UpdateApisNoUpdateSelf(apis []*Api, sc *config.ServerConfig) error {
	var err1, err2 error
	// 处理所有的 policy
	rules := [][]string{}
	// TODO maxWorker=1 的目的是 casbin 底层 addpolicy 方法线程不安全，操作本地 map 的行为
	wp := workerpool.New(1)
	for _, api := range apis {
		api := api
		wp.Submit(func() {
			oneRule := []string{
				obj.RoleValue,
				api.Path,
				api.Method,
			}
			// 处理ALL的情况
			if api.Method == "ALL" {
				methods := []string{
					"GET",
					"POST",
					"DELETE",
				}
				for _, m := range methods {
					pass, err := CasbinCheckPermission(obj.RoleValue, api.Path, m)
					if pass {
						continue
					}
					_, err = CasbinAddOnePolicy(obj.RoleValue, api.Path, m)
					if err != nil {
						sc.Logger.Error("CasbinAddOnePolicy错误",
							zap.Error(err),
							zap.String("角色", obj.RoleValue),
							zap.String("api.Path", api.Path),
							zap.String("api.Method", api.Method),
						)
					}
				}
			} else {
				pass, err := CasbinCheckPermission(obj.RoleValue, api.Path, api.Method)
				if pass {
					return
				}
				_, err = CasbinAddOnePolicy(obj.RoleValue, api.Path, api.Method)
				if err != nil {
					sc.Logger.Error("CasbinAddOnePolicy错误",
						zap.Error(err),
						zap.String("角色", obj.RoleValue),
						zap.String("api.Path", api.Path),
						zap.String("api.Method", api.Method),
					)
				}
			}
			rules = append(rules, oneRule)
		})
	}
	wp.StopWait()
	// TODO? 这里的CasbinAddPolicies的意义在哪里？
	//_, err3 := CasbinAddPolicies(rules)

	if err1 == nil && err2 == nil {
		return nil
	} else {
		return fmt.Errorf("更新关联:%w", err2)
	}
}

// 删除角色，包含其所有关联
func (obj *Role) DeleteOne() error {
	return Db.Select(clause.Associations).Unscoped().Delete(obj).Error
}
